从 fesacr 源码中总结出的 Java 代码编写注意事项

最近在看 fesacr 的源码, 从中也看到有一些代码书写不规范或是有误的地方.本文总结了自己看源码过程中目前发现的一些问题, 都已在 github 上提了 issue 或 pr.

字符串格式化

在 Java API 中一般有两种方式对字符串进行格式化:

  • String.format()
  • MessageFormat.format()

以上两种方法的主要差异其实就是要格式化的字符串中的占位符, String.format() 使用类似于 %s(表示字符)这样的占位符, MessageFormat.format() 使用 {0}({参数索引}).

而我们在项目开发中, 都会使用日志框架, 在打印日志的时候, 也会使用占位符进行格式化字符串, 占位符一般为 {}, 所以在项目开发过程中可能会搞错这几种方式, 搞混了这几种占位符.所以需要格外的注意.

对于 String.format() 而言, IDEA 其实已经提供了较好的验证支持,

String.format()占位符错误警告

相关 issue 可见: https://github.com/alibaba/fescar/issues/122

不要使用 File.rename() 对文件重命名

如果我们想对文件进行重命名, 首先想到的就是使用 File.rename() 方法, 但是仔细看看此方法的说明, 其实这个方法是不靠谱的, 也就是说有可能会失败.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Renames the file denoted by this abstract pathname.
*
* <p> Many aspects of the behavior of this method are inherently
* platform-dependent: The rename operation might not be able to move a
* file from one filesystem to another, it might not be atomic, and it
* might not succeed if a file with the destination abstract pathname
* already exists. The return value should always be checked to make sure
* that the rename operation was successful.
*
* <p> Note that the {@link java.nio.file.Files} class defines the {@link
* java.nio.file.Files#move move} method to move or rename a file in a
* platform independent manner.
*/

在几年前, 在用 Jetty 的时候, 也是遇到过这个问题.后来排查原因, 就是因为 File.rename() 引起的.所以最好不要使用此方法重命名.可以使用 Files.move() 或者是 apache commons-io 包中的方法.

相关 issue 可见: https://github.com/alibaba/fescar/issues/92

关闭 io 流的时候避免冗余的 close() 方法调用

有如下一个获取文件 channel 的例子:

1
2
3
4
5
6
File file = new File(...);
RandomAccessFile raf = new RandomAccessFile(file, "rw");
// 获取 channel
FileChannel fileChannel = raf.getChannel();
// 写数据
fileChannel.write(...);

一般我们可以通过上述的方式写入数据到文件中, 在最后关闭的时候, 大部分时候可能是为了保险起见, 会使用如下的代码关闭相关的资源:

1
2
fileChannel.close();
raf.close();

但是如下仔细查看 RandomAccessFile.close() 方法的源码, 就会发现, 其实这个方法的内部会自动调用 fileChannel.close():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
// 自动关闭 channel
if (channel != null) {
channel.close();
}

fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}

从上面可以看出, 关闭文件相关资源的时候, 只需要调用 RandomAccessFile.close(), 而不需要再调用一次 fileChannel.close().

相关 issue 可见: https://github.com/alibaba/fescar/issues/127

Netty 服务端设置 SO_KEEPALIVE 是没有用的

在用 Netty 进行网络编程的时候, 通过会设置 SO_KEEPALIVE 选项为 true:

1
2
3
4
5
6
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupWorker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, nettyServerConfig.getSoBackLogSize())
.option(ChannelOption.SO_REUSEADDR, true)
// 设置 SO_KEEPALIVE 选项
.option(ChannelOption.SO_KEEPALIVE, true)

初看上面的代码感觉是没有任何问题的, 有可能项目跑起来的时候也注意不到有什么问题.看如果真的细看启动日志就会发现, 使用上面的代码启动后, 会打印如下的警告日志:

1
WARN io.netty.bootstrap.ServerBootstrap - Unknown channel option 'SO_KEEPALIVE' for channel ...

这个问题的解决方案也很简单, 应该使用 childOption() 方法.这一点比较难以察觉的, 有时候需要观察下启动日志.

相关 issue 可见: https://github.com/alibaba/fescar/issues/150

第2章-分解策略
vscode github 图床插件 markdown-pic2github 改造